package com.liaoyifan.core.generator;

import com.liaoyifan.core.converter.HikariConverter;
import com.liaoyifan.core.properties.HikariProperties;
import com.liaoyifan.core.util.Try;
import com.liaoyifan.core.util.Upload;
import com.liaoyifan.core.valid.Lang;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import freemarker.template.Configuration;
import java.io.File;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.OpenOption;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.output.WriterOutputStream;
import org.springframework.boot.env.YamlPropertySourceLoader;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;

public class GenerateUtil {

    private static final Configuration CONFIG;
    private static final String USER_DIR;
    private static final String SEPARATOR;
    private static final Map<String, Class<?>> TYPE_MAPPING;

    static {
        CONFIG = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
        CONFIG.setClassLoaderForTemplateLoading(Generator.class.getClassLoader(), "/template");
        USER_DIR = System.getProperty("user.dir");
        SEPARATOR = FileSystems.getDefault().getSeparator();
        TYPE_MAPPING =
                Map.ofEntries(
                        // 整数类型
                        Map.entry("bigint", Long.class),
                        Map.entry("int", Integer.class),
                        Map.entry("integer", Integer.class),
                        Map.entry("smallint", Integer.class),
                        Map.entry("tinyint", Integer.class),
                        // 字符串类型
                        Map.entry("varchar", String.class),
                        Map.entry("char", String.class),
                        Map.entry("text", String.class),
                        Map.entry("longtext", String.class),
                        Map.entry("mediumtext", String.class),
                        // 浮点类型
                        Map.entry("double", Double.class),
                        Map.entry("float", Float.class),
                        Map.entry("decimal", BigDecimal.class),
                        // 时间类型
                        Map.entry("datetime", LocalDateTime.class),
                        Map.entry("timestamp", LocalDateTime.class),
                        Map.entry("date", LocalDate.class),
                        // 布尔类型
                        Map.entry("boolean", Boolean.class),
                        Map.entry("bit", Boolean.class));
    }

    public static Class<?> getJavaType(String fieldType) {
        if (fieldType == null || fieldType.trim().isBlank()) {
            return Object.class;
        }
        return TYPE_MAPPING.getOrDefault(extractType(fieldType).toLowerCase(), Object.class);
    }

    public static String extractType(String fieldType) {
        Pattern pattern = Pattern.compile("^[a-zA-Z]+");
        Matcher matcher = pattern.matcher(fieldType.trim());
        if (matcher.find()) {
            return matcher.group();
        }
        return fieldType.trim();
    }

    public static String getDataSourceProperty(PropertySource<?> property, String key) {
        String prefix = "liaoyifan.datasource.hikari.master.";
        return Objects.requireNonNull(property.getProperty(prefix + key)).toString();
    }

    public static HikariDataSource getDataSource(String jdbcUrl, String username, String password) {
        HikariProperties properties = new HikariProperties();
        properties.setJdbcUrl(jdbcUrl);
        properties.setUsername(username);
        properties.setPassword(password);
        HikariConfig config = HikariConverter.INSTANCE.convert(properties);
        return Try.run(() -> new HikariDataSource(config));
    }

    public static String findTableComment(String ddl) {
        int index = ddl.lastIndexOf("COMMENT='");
        if (index < 0) {
            return "";
        }
        String comment = ddl.substring(index + 9);
        comment = comment.substring(0, comment.length() - 1);
        return comment;
    }

    @SuppressWarnings("OptionalGetWithoutIsPresent")
    public static PropertySource<?> getPropertySource(String yml) {
        return Try.run(
                () ->
                        Arrays.stream(
                                        new PathMatchingResourcePatternResolver()
                                                .getResources(
                                                        ResourcePatternResolver
                                                                        .CLASSPATH_ALL_URL_PREFIX
                                                                + yml))
                                .map(
                                        i ->
                                                Try.run(
                                                        () -> {
                                                            File file = i.getFile();
                                                            return new YamlPropertySourceLoader()
                                                                    .load(
                                                                            file.getName(),
                                                                            new FileSystemResource(
                                                                                    file))
                                                                    .get(0);
                                                        }))
                                .findFirst()
                                .get());
    }

    public static String getDirPath(
            String basePackage, String moduleName, String filePackage, FileType fileType) {
        return USER_DIR
                + (".src.main."
                                + (fileType == FileType.XML ? "resources." : "java.")
                                + (fileType == FileType.XML
                                        ? "mapper." + moduleName
                                        : (basePackage + "." + moduleName + "." + filePackage)))
                        .replace(".", SEPARATOR)
                + SEPARATOR;
    }

    public static void generate(
            Map<String, Object> dataModel, Model model, FileType fileType, OpenOption... options) {
        String fileDir = model.name().toLowerCase();
        String dirPath =
                GenerateUtil.getDirPath(
                        String.valueOf(dataModel.get("basePackage")),
                        String.valueOf(dataModel.get("moduleName")),
                        fileType == FileType.IMPL ? fileDir + ".impl" : fileDir,
                        fileType);
        if (Lang.isEmpty(options)) {
            options = new StandardOpenOption[] {StandardOpenOption.CREATE_NEW};
        }
        String fileName =
                dataModel.get(fileDir + "Name").toString()
                        + (fileType == FileType.JAVA
                                ? ".java"
                                : fileType == FileType.XML ? ".xml" : "Impl.java");
        String templateName =
                (fileType == FileType.JAVA
                        ? fileDir
                        : fileType == FileType.XML ? fileDir + ".xml" : fileDir + "Impl");
        OpenOption[] openOptions = options;
        Try.run(
                () -> {
                    Upload.createDirIfNotExists(Paths.get(dirPath));
                    CONFIG.getTemplate(templateName + ".ftl")
                            .process(
                                    dataModel,
                                    WriterOutputStream.builder()
                                            .setPath(dirPath + fileName)
                                            .setOpenOptions(openOptions)
                                            .setCharset(StandardCharsets.UTF_8)
                                            .getWriter());
                });
    }
}
